iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
AI Engineering

打造具備記憶與執行能力的常駐 AI Agent:Hermes Agent × Gemini × MCP 的 Harness 設計系列 第 17 篇

【Day 17】Curator 的異動紀錄:一次封存與回復留下三筆紀錄

  • 分享至 

  • xImage
  •  

昨天量的是記憶那一層,寫入由模型決定、核准閘門預設關閉。同一套自我改進機制在技能這一側有一個背景程序在跑,它會標記、封存、整併它管理範圍內的技能。今天把它跑起來,然後故意封存一個技能再回復,看整條流程留下哪些紀錄。以下依序釐清三件事:它管的範圍到哪裡、一次實跑產生哪些檔案、回復之後的狀態與原本是否相同。


它管的是技能

hermes curator status 在還沒跑過任何一輪時的輸出:

curator: ENABLED
  runs:           0
  last summary:   deferred first run — curator seeded, will run after one interval
  interval:       every 7d
  stale after:    30d unused
  archive after:  90d unused
  consolidate:    off (prune-only; LLM merge pass opt-in)

curator-managed skills: 58 total  (agent-created=0  bundled=58)
  active     58
  stale      0
  archived   0

組態集中在 curator 這個區段:

鍵 預設值 作用
interval_hours 168 兩輪之間的間隔,等於 7 天
min_idle_hours 2 agent 閒置這麼久之後才跑
stale_after_days 30 未使用滿這麼多天標為 stale
archive_after_days 90 未使用滿這麼多天移進 .archive/
consolidate False LLM 整併那一趟預設關閉
prune_builtins True 內建技能也納入閒置封存
archive_ttl_days 0 封存內容永久保留
backup.keep 5 保留最近五份全樹快照

consolidate 關閉時一輪只做確定性的閒置修剪,輔助模型維持在旁待命,這一輪的 API 成本是零。註解另外說明封存做的是搬移,真正的刪除一律由明確的指令發動。

同一份檔案裡有三處在講管理範圍:

  • 區段開頭的註解:只審查 agent 建立的技能,從不動內建與 hub 安裝的技能
  • prune_builtins 的註解:內建技能同樣累積使用遙測,閒置時鐘從 curator 第一次看到它算起,hub 安裝的才永不修剪
  • status 的輸出:58 個內建技能全部列為 curator-managed

依預設值,內建技能也在管理範圍內。 hub 安裝的技能有外部的上游擁有者,這一類三處說法一致。


先預覽再實跑

--dry-run 只產生報告,狀態維持原狀:

curator: running DRY-RUN (report only, no mutations)...
curator: dry-run auto: no changes; llm: skipped (consolidation off)
auto (preview): 58 candidate skill(s) — no transitions applied in dry-run

拿掉旗標實際跑一輪,指令總共花 7.8 秒,run.json 記的審查時間是 6.79 秒:

curator: snapshot created (2026-09-22T03-12-56Z)
curator: auto: no changes; llm: skipped (consolidation off)
auto: checked=58 stale=0 archived=0 reactivated=0

一輪留下兩組檔案:

檔案 位置 大小
skills.tar.gz skills/.curator_backups/<UTC 時間戳>/ 920,012 B
manifest.json 同上 316 B
REPORT.md logs/curator/<本地時間戳>/ 827 B
run.json 同上 896 B

兩份機器可讀的紀錄各記不同的事:

  • manifest.json:skill_files: 58、reason: "pre-curator-run",另記 cron 任務的備份狀態
  • run.json:auto_transitions 是 checked: 58 與 seeded: 58,其餘歸零

seeded: 58 對應註解說的閒置時鐘從第一次看到算起,因此第一輪做的是登記。

REPORT.md 的標題行寫的是 Agent-created skills: 58 → 58 (+0),status 那一側同一批技能報的是 agent-created=0 bundled=58。同一批技能在兩份輸出裡掛在不同的分類名下。


一次封存留下一筆紀錄

skills.ledger 預設開啟,每一次技能變動附加一筆 JSONL 到 profile 目錄下的 skills/.curator_ledger.jsonl,檔案內容以 SHA-256 內容定址存進 .curator_backups/blobs/。註解標明它是遙測而非閘門,寫紀錄失敗擋不住變動本身。

手動封存一個內建技能:

$ hermes curator archive gif-search
curator: archived to ...\profiles\<profile 名稱>\skills\.archive\gif-search

$ hermes curator ledger
id             when         actor    action       skill
2d9ddd015fc4   1s ago       user     archive      gif-search

actor 欄位區分三種發動者,curator、agent 與 user,這一筆記的是 user。同時間量 prompt 的技能索引:

時點 索引 byte 索引裡的技能數
封存前 5,076 51
封存後 5,011 50

少掉的 65 B 對得上:這個技能的索引行 64 B,加上一個換行。


回復留下兩筆紀錄

單筆回復吃 ledger 的條目 id,執行前先把打算做的事列出來並要求確認:

$ hermes curator rollback 2d9ddd015fc4
Rollback target: ledger entry 2d9ddd015fc4
  action: archive
  skill:  gif-search
  actor:  user
  files:  3
Restore this mutation's before-state? [y/N]

帶 -y 執行之後的回報,把安全網也一起講明:

rolled back entry 2d9ddd015fc4 (archive on 'gif-search'): 2 file(s) restored, 1 removed. Safety entry 426d5e0e171c captured the pre-rollback state

一次封存加一次回復,ledger 累積三筆:

id actor action 內容
2d9ddd015fc4 user archive before 兩個路徑、after 一個路徑
426d5e0e171c agent pre-rollback 回復之前的狀態快照
18c62e2c0b43 agent rollback restored: 2、removed: 1

回復動作本身也進異動紀錄,而且先為自己留一份 pre-rollback。 三筆紀錄裡的每一個檔案引用都帶同一個 SHA-256,blob 目錄裡只有一個 2,720 B 的物件,內容定址的去重在這裡看得到。


回復之後多出一個技能

回復完成之後再量一次技能索引:

時點 索引 byte 索引裡的技能數
封存前 5,076 51
封存後 5,011 50
回復後 5,167 52

回復後比封存前多了 91 B,技能多了一個。 磁碟上的狀態有兩處與封存前不同:

  • 技能目錄多了一層巢狀:media/gif-search/SKILL.md 與 media/gif-search/media/gif-search/SKILL.md 兩個檔案各 2,720 B、內容相同,索引因此把同一個技能名列了兩次
  • 封存目錄留著:.archive/ 底下那個目錄已經沒有檔案,list-archived 仍然報得出這個技能名

原因在 ledger 那筆封存紀錄裡看得出來:它的 before 列了兩個路徑,兩個的 SHA-256 相同,其中一個就是那條巢狀路徑。封存前實際量到的是一個檔案、51 個索引條目。

回復動作重現的是異動紀錄裡的 before 狀態,而那份紀錄與封存前量到的狀態不一致

手動刪掉巢狀目錄與空的封存目錄之後,索引回到 5,076 B、51 個條目,list-archived 回報沒有封存的技能。


三條回復路徑與兩個核准閘門

路徑 粒度 指令
單筆變動回復 一次變動涉及的檔案 curator rollback <entry-id>
全樹快照回復 整個技能目錄 curator rollback --list 挑一份快照
封存區取回 一個技能 curator restore <name>,或直接 mv

archive_ttl_days 預設 0,封存內容永久保留,curator purge 是唯一會真正刪除的指令,只在明確下達時執行,並且記進 ledger。

寫入側的兩個閘門預設都是關閉:

  • skills.write_approval:False,開啟後技能寫入改為暫存待審,/skills pending 列出、/skills diff <id> 看完整差異、/skills approve 或 /skills reject 結案
  • memory.write_approval:False,開啟後前景寫入即時詢問,背景審查分支的寫入改為暫存

兩個閘門的設計取向一致:前景寫入當場有人回答,背景審查分支的寫入因此改為暫存待審。


心得

這套異動紀錄的設計比我預期的完整。回復自己也留紀錄、回復之前先快照、刪除要明確下令、封存永久保留,四件事合起來讓每一次變動都有回頭路。blob 用內容定址去重,同一份檔案被三筆紀錄引用只存一次。

真正的收穫是那 91 B。整條流程的每一個指令都回報成功,ledger 三筆紀錄齊全,rollback 說了 restored 2、removed 1,數字全部一致,而回復出來的狀態與原本不同。差異不在流程,在那筆紀錄本身寫進去的 before 狀態。

這件事量得出來的原因是有第二個來源可以對照。技能索引的 byte 數來自 prompt 的組成,走的是異動紀錄之外的另一條路徑。

異動紀錄證明的是流程跑完,至於回到哪一個狀態,要靠紀錄之外的量測

前天那條 write–manage–read 迴圈裡的 manage,在技能這一側有完整的紀錄與三條回復路徑,驗證那一段則要靠紀錄之外的第二個量測。


明天

身分宣告寫在哪裡才具持久性,以及 SOUL.md 與脈絡注入屬於同一套機制的哪個位置。


上一篇
【Day 16】跨 session 取回狀態:一則 69 byte 的記憶要付多少 context
系列文
打造具備記憶與執行能力的常駐 AI Agent:Hermes Agent × Gemini × MCP 的 Harness 設計 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言